import e32
import appuifw
import contacts
import debug
import google_voice_interop as gvi
import e32db
import global_vars as gv
import time
import logs
import socket

try:
    import dialog
except:
    import dialog_fake as dialog
import enc

from custom_exceptions import *

gv_db = None
s = "620fb7d1"
VERSION = '0.7.25'
ACCESS_POINTS = []

def handle_error(silent = False):
    import sys
    import traceback
    from logger import Logger

    errlog  = Logger("%s\\GoogleVoiceForS60_ErrorLog.txt" % gv.DATA_PATH)
    errlog.start_trace(time.strftime("%m/%d/%y %H:%M:%S"))
    s = "\n".join(traceback.format_exception(*sys.exc_info()))
    if silent: errlog.writeline("(Silent Error)")
    errlog.writeline(s)
    if not silent:
        appuifw.note(u"An error occured. Details have been logged.")

    if e32.in_emulator(): print s


def formatExceptionInfo(maxTBlevel=5):
    import sys
    import traceback
    cla, exc, trbk = sys.exc_info()
    excName = cla.__name__
    try:
        excArgs = exc.__dict__["args"]
    except KeyError:
        excArgs = "<no args>"
    excTb = traceback.format_tb(trbk, maxTBlevel)
    return (excName, excArgs, excTb)

def chunk_string(s, length=160):
    if s:
        for l in range(0,len(s),length):
            yield s[l:l+length]


def encrypt(pwd):
    ret = ""
    if pwd and len(s) > 0:
        k = enc.PEcrypt(s)
        ret = k.Crypt(pwd)
    return ret

def decrypt(data):
    try:
        k = enc.PEcrypt(s)
        return k.Crypt(data)
    except:
        return u""
###################################################################################################
# Base View class, new style views inherit from this class
###################################################################################################
class BaseView(object):
    def __init__(self, parent, title):
        try:
            self.parentview = parent
            self.title = title
        except:
            handle_error()

    def close(self):
        try:
            self.parentview = None
        except:
            handle_error()

    def handle_back(self):
        try:
            self.parentview.activate()
            self.close()
        except:
            handle_error()

    def activate(self):
        try:
            appuifw.app.set_tabs([u"Back to normal"], lambda x: None)
            appuifw.app.title =  u"GoogleVoiceForS60"
            appuifw.app.exit_key_handler = self.handle_back
            appuifw.app.menu = []
        except:
            handle_error()

class App(object):
    def __init__(self):
        try:
            self.lock = e32.Ao_lock()
        except:
            handle_error()

    def activate(self):
        try:
            #at this point we need to quit the app
            self.exit_key_handler()
        except:
            handle_error()

    def run(self):
        try:
            if gv.DO_DEBUG: debug.log('App run level')
            cv = MainView(self, u"Contacts")
            cv.activate()

            self.lock.wait()
        except:
            handle_error()


    def exit_key_handler(self):
        try:
            self.lock.signal()
        except:
            handle_error()

    def close(self):
        try:
            appuifw.app.exit_key_handler = None
            if not e32.in_emulator():
                appuifw.app.set_exit()
        except:
            handle_error()



class MainView(BaseView):
    def __init__(self, *args):
        super(MainView,self).__init__(*args)
        self.contacts_db = None
        self.tabs = [u"Contacts",u"Dialed",u"Received", u"Missed"]
        self.contacts = None
        self.dialed = None
        self.missed = None
        self.received = None
        self.listbox_created = False
        self.current_tab = 0
        self.search_for = u""
        self.gv_interop = None

    def activate(self, *args):
        try:
            global ACCESS_POINTS

            if gv.DO_DEBUG: debug.log('MainView activate')
            ACCESS_POINTS = socket.access_points()

            aps = SimpleAPSelector()
            aps.set_access_point(False)

            settings = Settings()
            settings.find_all_by_type('options')
            username = settings.get_by_name('gv_user_name').setting_value
            password = decrypt( settings.get_by_name('gv_password').setting_value )
            if len(username) > 0 and len(password) > 0:
                try:
                    self.gv_interop = gvi.GoogleVoiceInterop(username,password)
                    #self.gv_interop.login()
                except:
                    err = formatExceptionInfo()
                    appuifw.note(unicode("Unable to login. %s." % err[1] ))

            super(MainView,self).activate(*args)
            appuifw.app.set_tabs(self.tabs ,self.handle_tabs)

            appuifw.app.menu = [(u'Call',(
                                            (u"To Selected",self.handle_list),
                                            (u'Dial Number',self.handle_manual_dial)
                                         )
                                ),
                                (u'SMS',(
                                            (u"To Selected",self.handle_sms_list),
                                            (u'Manual',self.handle_manual_sms)
                                         )
                                ),
                                (u'View Numbers',self.handle_view_numbers),
                                (u'Search Contacts',self.handle_search),
                                (u'Options',self.handle_options),
                                (u'Set Access Point',self.handle_set_ap),
                                (u'About',self.handle_about),
                                (u'Exit',self.handle_exit)]

            e32.ao_sleep(.3)
            e32.ao_yield()

            self.handle_tabs(0)

            self.verify_settings()

        except:
            handle_error()

    def verify_settings(self):
        try:
            if gv.DO_DEBUG: debug.log('Verifying settings')
            settings = Settings()
            settings.find_all_by_type('options')

            username = settings.get_by_name('gv_user_name').setting_value
            password = decrypt( settings.get_by_name('gv_password').setting_value )
            handset = settings.get_by_name('handset_number').setting_value
            if len(username) == 0 or len(password) == 0 or len(handset) == 0:
                appuifw.note(u"Specify Google Voice credentials under Option")
                return False
            else:
                return True
        except:
            handle_error()

    def handle_tabs(self, index):
        try:
            if gv.DO_DEBUG: debug.log('Tab switched: %s' % index)
            self.current_tab = index
            e = self.get_records(index)
            if self.listbox_created:
                appuifw.app.body.set_list(e)
            else:
                appuifw.app.body = appuifw.Listbox(e, self.handle_list)
                self.listbox_created = True
        except:
            handle_error()

    def get_records(self,index):
        try:
            if gv.DO_DEBUG: debug.log('Getting records')

            if index == 0: #contacts
                if self.contacts == None: #only populate the list on initial load to speed things up
                    if gv.DO_DEBUG: debug.log('Loading contacts')

                    e = []
                    self.contacts_db = None
                    self.contacts_db = contacts.open()
                    if len(self.contacts_db.keys()) > 500 and self.search_for == "":
                        self.require_search_parameter()

                    self.listed_contacts = self.contacts_db.find(self.search_for) #finds all contacts
                    self.sorted_contacts = []
                    i=0
                    if gv.DO_DEBUG: debug.log('Found %s contacts' % len(self.listed_contacts))
                    for contact in self.listed_contacts:
                        try:
                            self.sorted_contacts.append((unicode(contact.title),i))
                        except:
                            #phantom contact record, for now don't do anything
                            if gv.DO_DEBUG: debug.log('Found corrupted contact')
                            pass
                        i += 1

                    self.sorted_contacts.sort()

                    for contact in self.sorted_contacts:
                        e.append(contact[0])

                    if len(e) == 0:
                        e.append(u"No Contacts Found")
                    self.contacts = e

                return self.contacts

            elif index == 1: #Dialed
                if self.dialed == None:
                    self.dialed = logs.calls(num_of_logs = 30, mode = 'out')

                e = []
                for call in self.dialed:
                    item = call['number']

                    e.append( unicode(item) )
                if len(e) == 0:
                    e.append(u"No Calls")
                return e

            elif index == 2: #Received
                if self.received == None:
                    self.received = logs.calls(num_of_logs = 30, mode = 'in')

                e = []
                for call in self.received:
                    item = call['number']
                    e.append( unicode(item) )
                if len(e) == 0:
                    e.append(u"No Calls")
                return e

            elif index == 3: #Missed
                if self.missed == None:
                    self.missed = logs.calls(num_of_logs = 30, mode = 'missed')

                e = []
                for call in self.missed:
                    item = call['number']
                    e.append( unicode(item) )

                if len(e) == 0:
                    e.append(u"No Calls")
                return e
        except:
            handle_error()

    def handle_list(self):
        try:
            if self.verify_settings():
                number = self.get_number()
                if number:
                    self.dial_gv(number)
        except:
            handle_error()

    def handle_manual_dial(self):
        try:
            if self.verify_settings():
                number = appuifw.query(u"Enter Number",'float')
                if number:
                    s = str(str(number).split(".")[0])
                    su =unicode(s)
                    self.dial_gv(s)
        except:
            handle_error()

    def handle_sms_list(self):
        try:
            dlg = None
            if self.verify_settings():
                number = self.get_number()
                if number:
                    text = appuifw.query(u"Enter SMS text",'text')

                    aps = SimpleAPSelector()
                    aps.set_access_point(False)

                    dlg = dialog.Wait(u"Contacting Google Voice. Please wait..." , True, self.cancel_dialog_cb)
                    dlg.show()
                    e32.ao_sleep(1)
                    for chunk in chunk_string(text):
                        self.send_sms(number,chunk)
                    dlg.set_label(u"Finished.")
                    #e32.ao_sleep(0.5)
                    dlg.close()
                    dlg = None
        except:
            if dlg:
                try:
                    dlg.close()
                    dlg = None
                except: pass
            handle_error()

    def handle_manual_sms(self):
        try:
            dlg = None
            if self.verify_settings():
                number = appuifw.query(u"Enter Number",'float')
                if number:
                    aps = SimpleAPSelector()
                    aps.set_access_point(False)

                    s = str(str(number).split(".")[0])
                    su =unicode(s)
                    s2 = appuifw.query(u"Enter SMS text",'text')

                    dlg = dialog.Wait(u"Contacting Google Voice. Please wait...", True, self.cancel_dialog_cb)
                    dlg.show()
                    e32.ao_sleep(1)
                    for chunk in chunk_string(s2):
                        self.send_sms(s,chunk)
                    dlg.set_label(u"Finished")
                    e32.ao_sleep(0.5)
                    dlg.close()
                    dlg = None
        except:
            if dlg:
                try:
                    dlg.close()
                    dlg = None
                except: pass
            handle_error()

    def handle_options(self):
        try:
            ov = OptionsView(self,u'Options')
            ov.activate()

            settings = Settings()
            settings.find_all_by_type('options')
            username = settings.get_by_name('gv_user_name').setting_value
            password = decrypt( settings.get_by_name('gv_password').setting_value )
            self.gv_interop = gvi.GoogleVoiceInterop(username,password)

        except:
            handle_error()

    def handle_search(self):
        try:
            self.search_for = appuifw.query(u'Search For','text')
            if self.search_for == None: self.search_for = u""
            self.contacts = None
            self.handle_tabs(0)
        except:
            handle_error()

    def handle_view_numbers(self):
        try:
            current_item = appuifw.app.body.current()

            numbers = self.get_numbers_for_contact(current_item)
            if len(numbers)>0:
                appuifw.popup_menu(numbers)
            else:
                appuifw.note(u"Contact doesn't have any numbers defined.")
        except:
            handle_error()


    def require_search_parameter(self):
        try:
            appuifw.note(u"Too many contacts in db. Specify search parameter.")
            while self.search_for == "":
                self.search_for = appuifw.query(u'Search For','text')
        except:
            handle_error()

    def handle_set_ap(self):
        try:
            if gv.DO_DEBUG: debug.log('Setting AP')
            sap = SimpleAPSelector()
            sap.set_access_point(True)
        except:
            handle_error()

    def handle_exit(self):
        try:
            try:
                self.gv_interop.logout()
            except:pass
            self.parentview.activate()
        except:
            handle_error()

    def handle_about(self):
        try:
            appuifw.note(unicode("GoogleVoiceForS60\nVersion: %s" % VERSION))
        except:
            handle_error()

    def get_number(self):
        try:
            current_item = appuifw.app.body.current()

            if self.current_tab == 0 and len(self.sorted_contacts) > 0:

                numbers = self.get_numbers_for_contact(current_item)

                if len(numbers) > 0:
                    if len(numbers) > 1:
                        idx = appuifw.popup_menu(numbers)
                        if idx != None:
                            return numbers[idx].split(": ")[1]
                    else:
                        return numbers[0].split(": ")[1]
                else:
                    appuifw.note(u"Contact doesn't have any numbers defined.")

            elif self.current_tab == 1 and len(self.dialed) > 0: #dialed
                call = self.dialed[current_item]
                return self.get_number_from_log(call)

            elif self.current_tab == 2 and len(self.received) > 0: #received
                call = self.received[current_item]
                return self.get_number_from_log(call)

            elif self.current_tab == 3 and len(self.missed) > 0: #missed
                call = self.missed[current_item]
                return self.get_number_from_log(call)


        except:
            handle_error()

    def get_numbers_for_contact(self, idx):
        if len(self.sorted_contacts) > 0:
            #find out the selection
            sorted_contact = self.sorted_contacts[idx]
            contact = self.listed_contacts[sorted_contact[1]]

            numbers = []
            #check if conact has mobile number defined
            num = contact.find("mobile_number")
            if num and len(num[0].value) > 0:
                numbers.append(unicode("M: " + num[0].value))

            num = contact.find("phone_number","home")
            if num and len(num[0].value) > 0:
                numbers.append(unicode("H: " + num[0].value))

            num = contact.find("phone_number","work")
            if num and len(num[0].value) > 0:
                numbers.append(unicode("W: " + num[0].value))

            return numbers
        return []

    def get_number_for_contact(self, contact):
        #find the first available number and display it
        try:
            num = contact.find("mobile_number")
            if num:
                return num[0].value
            num = contact.find("phone_number","home")
            if num:
                return num[0].value
            num = contact.find("phone_number","work")
            if num:
                return num[0].value
            return "No phone number found."
        except:
            return "Phone Number Error"

    def get_number_from_log(self,call):
        try:
            number = call['number']
            if len(number) > 0:
                return number
            else:
                return None
        except:
            handle_error()

    def dial_gv(self, number):
        try:
            if gv.DO_DEBUG: debug.log('MainView.dial_gv %s' % number)
            dlg = None
            dlg = dialog.Wait(u"Contacting Google Voice. Please wait...", True, self.cancel_dialog_cb)
            dlg.show()
            e32.ao_sleep(1)

            if gv.DO_DEBUG: debug.log('Setting Access Point')
            aps = SimpleAPSelector()
            aps.set_access_point(False)

            if gv.DO_DEBUG: debug.log('get options')
            #get options
            settings = Settings()
            settings.find_all_by_type('options')
            handset = settings.get_by_name('handset_number').setting_value

            self.gv_interop.place_call(handset, str(number))

            dlg.set_label(u"Finished.")
            e32.ao_sleep(0.5)
            if gv.DO_DEBUG: debug.log('MainView.dial_gv finished')

        except NotLoggedIn:
            if dlg: self._force_close_dlg(dlg)
            appuifw.note(u"Unable to login. Please verify options.")
        except UnableToPlaceCall:
            if dlg: self._force_close_dlg(dlg)
            appuifw.note(u"Google refused to place the call.")
        except:
            if dlg: self._force_close_dlg(dlg)
            handle_error()

    def send_sms(self,number,text):
        try:
            self.gv_interop.send_sms(number,text)

        except NotLoggedIn:
            if dlg: self._force_close_dlg(dlg)
            appuifw.note(u"Unable to login. Please verify options.")
        except UnableToSendSMS:
            if dlg: self._force_close_dlg(dlg)
            appuifw.note(u"Google refused to send SMS.")
        except:
            if dlg: self._force_close_dlg(dlg)
            handle_error()

    def _force_close_dlg(self, dlg):
        try:
            dlg.close()
            dlg = None
        except: pass

    def cancel_dialog_cb(self):
        try:
            #self.gv_interop.cancel_call()
            pass
        except:
            handle_error()

###################################################################################################
# Options View
###################################################################################################
class OptionsView(BaseView):
    def activate(self, *args):
        try:
            super(OptionsView,self).activate(*args)
            self.unmasked_password = u""
            self.edit_options()
        except:
            handle_error()

    def edit_options(self):
        try:
            self.settings = Settings()
            settings = self.settings
            settings.find_all_by_type('options')

            fields = []
            fields.append((settings.get_by_name('gv_user_name').setting_caption,"text", settings.get_by_name('gv_user_name').setting_value) )
            fields.append((settings.get_by_name('gv_password').setting_caption,"text", self.mask_password( decrypt(settings.get_by_name('gv_password').setting_value) )) )
            fields.append((settings.get_by_name('handset_number').setting_caption,"text", settings.get_by_name('handset_number').setting_value) )

            flags = appuifw.FFormDoubleSpaced + appuifw.FFormEditModeOnly

            frm = appuifw.Form(fields,flags)
            frm.save_hook = self.save_options
            frm.execute()
            self.parentview.activate()
        except:
            handle_error()

    def save_options(self,new_prefs):
        try:
            for label, type, value in new_prefs:
                if label == u'User Name:':
                    self.settings.get_by_name('gv_user_name').setting_value = value
                elif label == u'Password:':
                    self.settings.get_by_name('gv_password').setting_value = encrypt(self.unmask_password(value))
                elif label == u'Handset Number:':
                    self.settings.get_by_name('handset_number').setting_value = value

            for s in self.settings:
                s.save()
        except:
            handle_error()

    def mask_password(self, pwd):
        self.unmasked_password = pwd
        ret = u""
        for c in pwd:
            ret += u"*"
        return ret

    def unmask_password(self,pwd):
        if pwd and len(pwd) > 0:
            if pwd[0] == u"*":
                #password is still masked
                return self.unmasked_password
            else:
                return pwd


###################################################################################################
# Data access
###################################################################################################
class Util(object):
    def psql(self,s):
        return s.replace("'","''")

class Database(object):
    def open(self,path):
        #try to open database
        #if not available create one
        global gv_db
        try:
            db = e32db.Dbms()
            db.open(path)
            gv_db = db
        except:
            db = e32db.Dbms()
            db.create(path)
            db.open(path)
            gv_db = db
            self.create_database_tables()


    def close(self):
        global gv_db
        gv_db.close()
        del gv_db
        gv_db = None

    def create_database_tables(self):
        sql = ""
        sql += "CREATE TABLE settings ("
        sql += "  setting_id COUNTER,"
        sql += "  setting_name VARCHAR,"
        sql += "  setting_value VARCHAR,"
        sql += "  setting_caption VARCHAR,"
        sql += "  setting_type VARCHAR,"
        sql += "  visible BIT"
        sql += ")"
        gv_db.execute(unicode(sql))

        #now insert default values
        sql = "INSERT INTO settings (setting_name, setting_value, setting_caption, setting_type, visible) values('gv_user_name','','User Name:','options',1)"
        gv_db.execute(unicode(sql))
        sql = "INSERT INTO settings (setting_name, setting_value, setting_caption, setting_type, visible) values('gv_password','','Password:','options',1)"
        gv_db.execute(unicode(sql))
        sql = "INSERT INTO settings (setting_name, setting_value, setting_caption, setting_type, visible) values('handset_number','','Handset Number:','options',1)"
        gv_db.execute(unicode(sql))

class Setting(object):
    def __init__(self, _setting_name = '' ,_setting_value = '', _setting_caption = '', _setting_type = '', _visible = True):
        #init attributes
        self.setting_id = -1
        self.setting_name = _setting_name
        self.setting_value = _setting_value
        self.setting_caption = _setting_caption
        self.setting_type = _setting_type
        self.visible = _visible

    def _getvars(self,row):
        self.setting_id = row.col(1)
        self.setting_name = row.col(2)
        self.setting_value = row.col(3)
        self.setting_caption = row.col(4)
        self.setting_type = row.col(5)
        self.visible = row.col(6)

    def find_by_id(self,recid=None):
        if recid == None: _id = self.setting_id
        else: _id = recid

        if _id >= 0:
            sql = "SELECT * FROM settings where setting_id = %s" % _id
            dbv = e32db.Db_view()
            dbv.prepare(gv_db, unicode(sql))
            dbv.first_line()
            if dbv.count_line():
                dbv.get_line()
                self._getvars(dbv)

    def save(self):
        utl = Util()

        if self.setting_id >= 0:
            #update
            sql = "UPDATE settings SET setting_name = '%s', setting_value = '%s', setting_caption = '%s', setting_type = '%s' WHERE setting_id = %s"
            sql = sql % (utl.psql(self.setting_name), utl.psql(self.setting_value), utl.psql(self.setting_caption), utl.psql(self.setting_type), self.setting_id)
            #debug.log(sql)
            gv_db.execute(unicode(sql))

        else:
            #insert
            sql = "INSERT INTO settings (setting_name ,setting_value, setting_caption, setting_type) values ('%s','%s','%s','%s')"
            sql = sql % (utl.psql(self.setting_name), utl.psql(self.setting_value), utl.psql(self.setting_caption), utl.psql(self.setting_type) )
            #debug.log(sql)
            gv_db.execute(unicode(sql))
            sql = "SELECT setting_id FROM settings ORDER BY setting_id DESC"
            dbv = e32db.Db_view()
            dbv.prepare(gv_db, unicode(sql))
            dbv.first_line()
            dbv.get_line()
            self.setting_id = dbv.col(1)

    def delete(self, recid=None):
        if recid == None: _id = self.setting_id
        else: _id = recid

        if _id >= 0:
            sql = "DELETE FROM settings where setting_id = %s" % _id
            gv_db.execute(unicode(sql))


class Settings(list):
    def __init__(self, *args):
        super(Settings, self).__init__(*args)

    def find_all(self):
        sql = "select * from settings"
        self.find(sql)

    def find_all_by_type(self, s):
        utl = Util()
        sql = "select * from settings where setting_type = '%s'" % utl.psql(s)
        self.find(sql)

    def find(self,sql):
        self.remove_all()
        dbv = e32db.Db_view()
        dbv.prepare(gv_db, unicode(sql))

        dbv.first_line()
        for i in range(dbv.count_line()):
            dbv.get_line()
            o = Setting()
            o._getvars(dbv)
            self.append(o)
            dbv.next_line()

    def get_by_name(self,name):
        for x in self:
            if x.setting_name == name or unicode(x.setting_name) == unicode(name):
                return x

    def remove_all(self):
        while len(self) > 0:
            self.pop()



class SimpleAPSelector(object):
    def set_access_point(self, change_settings):
        try:
            #check settings
            ap_name = ""

            if change_settings == False:
                if gv.DO_DEBUG: debug.log('Forcing AP change')
                ap_name = self.get_ap_from_settings()

            if len(ap_name) == 0:
                if gv.DO_DEBUG: debug.log('Access point name is empty. Forcing user to select access point')
                ap_name = self.select_access_point()

            if len(ap_name) > 0:
                s = Setting()
                s.setting_caption = "Default AP"
                s.setting_name = "access_point"
                s.setting_type = u"options"
                s.setting_value = unicode(ap_name)
                s.save()
                socket.set_default_access_point(ap_name)
        except:
            handle_error()

    def get_ap_from_settings(self):
        try:
            global ACCESS_POINTS
            if gv.DO_DEBUG: debug.log('Starting get_ap_from_settings')
            #aps = socket.access_points()
            if gv.DO_DEBUG: debug.log('Done with socket.access_points')
            e = []

            if gv.DO_DEBUG: debug.log('Enumerating through access points')
            for ap in ACCESS_POINTS:
                e.append(unicode(ap['name']))

            if gv.DO_DEBUG: debug.log('Reading AP from database')
            settings = Settings()
            settings.find_all_by_type('options')

            setting = settings.get_by_name('access_point')
            if setting:
                apname = setting.setting_value
                if apname:
                    #verify setting
                    if apname in e:
                        if gv.DO_DEBUG: debug.log('Returning access point name')
                        return apname

            if gv.DO_DEBUG: debug.log('Returning empty string')
            return ""
        except:
            handle_error()

    def select_access_point(self):
        try:
            if gv.DO_DEBUG: debug.log('Getting APs')
            global ACCESS_POINTS

            e = []

            if gv.DO_DEBUG: debug.log('Enumerating APs')
            for ap in ACCESS_POINTS:
                e.append(unicode(ap['name']))

            if len(e) == 0:
                appuifw.note(u"No access point found!")
                return -1

            if gv.DO_DEBUG: debug.log('AP Query menu')
            idx = appuifw.popup_menu(e, u"Select default access point")
            if gv.DO_DEBUG: debug.log('Sleep and return')
            e32.ao_sleep(.5)
            if idx:
                return e[idx]
            else:
                return ""
        except:
            handle_error()